/* 
 * File:   Heuristic.cpp
 * Author: Guilherme Monteiro
 */

#include "../headers/Heuristic.h"

Heuristic::Heuristic(ROI *r, MetaInfo* meta, GenericModel* model) {
    setROI(r);
    this->meta = meta;
    this->model = model;
    this->filter = new FilterLines(this->_roi);
    configureHeuristicROI();
    
    //setVerticalForegroundLines(detectForegroundVerticalLines());
    //setHorizontalForegroundLines(detectForegroundHorizontalLines());
    setVerticalBackgroundLines(detectBackgroundVerticalLines());
    setHorizontalBackgroundLines(detectBackgroundHorizontalLines());
}

Heuristic::Heuristic(const Heuristic& orig) {
}

Heuristic::~Heuristic() {
}

/**
 * Configures the roi in with
 * the heuristic is working.
 * 
 * @param r: roi
 */
void Heuristic::setROI(ROI* r) {
    this->_roi = r;
}

/**
 * Returns a pointer to
 * roi with the heuristic is working.
 * 
 * @return pointer to roi
 */
ROI* Heuristic::getROI() {
    return this->_roi;
}

/**
 * Returns the maximum value of projection.
 * 
 * @param horizontalProjection
 * @return maximum
 */
int Heuristic::getMaximum(vector<int> projection) {
    vector<int>::iterator it;
    int max = 0;
    for(it = projection.begin(); it != projection.end(); it++) {
        if(max < (*it)) {
            max = *it;
        }
    }
    return max;
}

/**
 * Get a minimum value present in projection.
 * 
 * @param projection
 * @return minimum
 */
int Heuristic::getMinimum(vector<int> projection) {
    vector<int>::iterator it;
    int min = 0;
    for(it = projection.begin(); it != projection.end(); it++) {
        if(min > (*it)) {
           min = *it;
        }
    }
    return min;
}

/**
 * Detect horizontal lines, both foreground and background, filtering
 * the skeleton of the table and organizing
 * the points.
 * 
 * @return vector of horizontal lines
 */
vector<Line> Heuristic::detectHorizontalLines() {
    vector<Line> horizontalLines;
    
    horizontalLines.push_back(this->_roi->getUpLineTable());
    
//    for(int i = 0; i < getHorizontalForegroundLines().size(); i++) {
//        horizontalLines.push_back(getHorizontalForegroundLines().at(i));
//    }
    for(int j = 0; j < getHorizontalBackgroundLines().size(); j++) {
        horizontalLines.push_back(getHorizontalBackgroundLines().at(j));
    }
    
    horizontalLines.push_back(this->_roi->getDownLineTable());
    
//    printf("horizontalLines.size: %d\n", (int)horizontalLines.size());
    
    return filter->secondaryFilter(horizontalLines);
}

/**
 * Detect vertical lines, both foreground and background, filtering
 * the skeleton of the table and organizing
 * the points.
 * 
 * @return vector of vertical lines
 */
vector<Line> Heuristic::detectVerticalLines() {
    vector<Line> verticalLines;
    
    verticalLines.push_back(this->_roi->getLeftLineTable());
    
//    for(int i = 0; i < getVerticalForegroundLines().size(); i++) {
//        verticalLines.push_back(getVerticalForegroundLines().at(i));
//    }
    for(int i = 0; i < getVerticalBackgroundLines().size(); i++) {
        verticalLines.push_back(getVerticalBackgroundLines().at(i));
    }
    
    verticalLines.push_back(this->_roi->getRightLineTable());
    
//    printf("verticalLines.size: %d\n", (int)verticalLines.size());
    
    return filter->secondaryFilter(verticalLines);
}

/**
 * Configures the image of roi
 * to be a gray scale image without noises and
 * configures the path to log image. Here the
 * image roi is solved into 'metadados/tabelasAlta'.
 * 
 * @param *r: pointer to roi
 */
void Heuristic::configureHeuristicROI() {
    ImageNoisesExtractor extractorNoises((*getROI()).getImageROI());
    IplImage* binaryImageWithoutNoises = extractorNoises.extractNoises();
    
//    cvShowImage("image before rotation", imageWithoutNoises);
//    cvResizeWindow("image before rotation", 600, 600);
//    cvNamedWindow("image before rotation");
//    cvWaitKey(0);
//    cvDestroyAllWindows();
    
//    RotationCorrector* corrector = new RotationCorrector();
//    IplImage* binaryImageWithRotationCorrected = corrector->correctsRotation(binaryImageWithoutNoises);
    (*this->getROI()).setImageROI(binaryImageWithoutNoises);
    
//    cvShowImage("image after rotation", getROI()->getImageROI());
//    cvResizeWindow("image after rotation", 600, 600);
//    cvNamedWindow("image after rotation");
//    cvWaitKey(0);
//    cvDestroyAllWindows();
    
    adaptAndSaveImageROICut(this->_roi); // save image in 'tabelasAlta' folder
    
    ImageColorInverter* inverter = new ImageColorInverter();
    imageROIWithoutDilatation = inverter->invert(getROI()->getImageROI());
    getROI()->setImageROI(imageROIWithoutDilatation);
    
    ImageDilatator* dilatator = new ImageDilatator(getROI()->getImageROI());
    IplImage* dilatedImage = dilatator->dilate();
    
    IplImage* binaryImageInRGB = cvCreateImage(cvGetSize(dilatedImage), IPL_DEPTH_8U, 3);
    cvCvtColor(dilatedImage, binaryImageInRGB, CV_GRAY2RGB);
    
    ImageHistogramBasedOnProjectionCreator* creator = new ImageHistogramBasedOnProjectionCreator(binaryImageInRGB);
    creator->run();
    
    cvReleaseImage(&binaryImageInRGB);
//    cvReleaseImage(&binaryImageWithRotationCorrected);
    cvReleaseImage(&binaryImageWithoutNoises);
    cvReleaseImage(&dilatedImage);
    
    setVerticalProjection(creator->getVerticalProjection());
    setHorizontalProjection(creator->getHorizontalProjection());
    
    setMaximumVertical(getMaximum(getVerticalProjection()));
    setMaximumHorizontal(getMaximum(getHorizontalProjection()));
    setMinimumVertical(getMinimum(getVerticalProjection()));
    setMinimumHorizontal(getMinimum(getHorizontalProjection()));
}

/**
 * Transcribe the lines specifically
 * to simple table images.  Detect the background and
 * foreground lines of table, that forms the skeleton of the table,
 * this skeleton already is formed to filtered coordinates,
 * that are returned neatly in a vector of cvPoints.
 * 
 * @param r: ROI about it acts
 */
vector< vector<Line> > Heuristic::transcribeLines() {
    vector< vector<Line> > linesDetected;
    
    linesDetected.push_back(detectVerticalLines()) ;
    linesDetected.push_back(detectHorizontalLines());
    
    return linesDetected;
}

/**
 * Cut the high resolution image
 * and save it in 'tabelasAlta' folder.
 */
void Heuristic::adaptAndSaveImageROICut(ROI* r) {
    //IplImage* imageReversed = cvCreateImage(cvGetSize(r->getImageROI()), IPL_DEPTH_8U, 1);
    //cvThreshold(r->getImageROI(), imageReversed, 0, 255, CV_THRESH_BINARY_INV);
    ImageSaver* saver = new ImageSaver(r->getImageROI(), r->getHighResolutionImageCutPath());
    saver->save();
    //cvReleaseImage(&imageReversed);
}

/**
 * Returns the filter used
 * in heuristic.
 * 
 * @return filter
 */
FilterLines* Heuristic::getFilter() {
    return this->filter;
}

/**
 * Save image roi clone and release it.
 * 
 * @param img
 */
void Heuristic::saveImageROI(IplImage* img) {
    ImageSaver* saver = new ImageSaver(img, this->_roi->getLogImagePath());
    saver->save();
}

/**
 * Sets the vertical projection.
 * 
 * @param verticalProjection
 */
void Heuristic::setVerticalProjection(vector<int> verticalProjection) {
    this->verticalProjection = verticalProjection;
}

/**
 * Returns the vertical projection.
 * 
 * @return vertical projection
 */
vector<int> Heuristic::getVerticalProjection() {
    return this->verticalProjection;
}

/**
 * Sets the horizontal projection.
 * 
 * @param horizontalProjection
 */
void Heuristic::setHorizontalProjection(vector<int> horizontalProjection) {
    this->horizontalProjection = horizontalProjection;
}

/**
 * Returns the horizontal projection.
 * 
 * @return horizontalProjection
 */
vector<int> Heuristic::getHorizontalProjection() {
    return this->horizontalProjection;
}

/**
 * Sets the vertical lines.
 * 
 * @param verticalLines
 */
void Heuristic::setVerticalLines(vector<Line> verticalLines) {
    this->verticalLines = verticalLines;
}

/**
 * Returns the vertical lines.
 * 
 * @return verticalLines
 */
vector<Line> Heuristic::getVerticalLines() {
    return this->verticalLines;
}

/**
 * Sets the horizontal lines.
 * 
 * @param horizontalLines
 */
void Heuristic::setHorizontalLines(vector<Line> horizontalLines) {
    this->horizontalLines = horizontalLines;
}

/**
 * Returns the horizontal lines.
 * 
 * @return horizontalLines
 */
vector<Line> Heuristic::getHorizontalLines() {
    return this->horizontalLines;
}

/**
 * Returns the maximum value of horizontal projection.
 * 
 * @return maximumHorizontal
 */
int Heuristic::getMaximumHorizontal() {
    return this->maximumHorizontal;
}

/**
 * Sets the maximum value of horizontal projection.
 * 
 * @param max
 */
void Heuristic::setMaximumHorizontal(int max) {
    this->maximumHorizontal = max;
}

/**
 * Returns the maximum value of vertical projection.
 * 
 * @return maximumVertical
 */
int Heuristic::getMaximumVertical() {
    return this->maximumVertical;
}

/**
 * Sets the maximum value of vertical projection.
 * 
 * @param max
 */
void Heuristic::setMaximumVertical(int max) {
    this->maximumVertical = max;
}

/**
 * Returns the minimum value of horizontal projection.
 * 
 * @return minimumHorizontal
 */
int Heuristic::getMinimumHorizontal() {
    return this->minimumHorizontal;
}

/**
 * Sets the minimum value of horizontal projection.
 * 
 * @param min
 */
void Heuristic::setMinimumHorizontal(int min) {
    this->minimumHorizontal = min;
}

/**
 * Returns the minimum value of vertical projection.
 * 
 * @return minimumVertical
 */
int Heuristic::getMinimumVertical() {
    return this->minimumVertical;
}

/**
 * Sets the minimum value of vertical projection.
 * 
 * @param min
 */
void Heuristic::setMinimumVertical(int min) {
    this->minimumVertical = min;
}

/**
 * Sets the horizontal background lines vector.
 * 
 * @param horizontalBackgroundLines
 */
void Heuristic::setHorizontalBackgroundLines(vector<Line> horizontalBackgroundLines2) {
    this->horizontalBackgroundLines = horizontalBackgroundLines2;
}

/**
 * Returns the horizontal background lines.
 * 
 * @return horizontalBackgroundLines
 */
vector<Line> Heuristic::getHorizontalBackgroundLines() {
    return this->horizontalBackgroundLines;
}

/**
 * Sets the vertical background lines vector.
 * 
 * @param verticalBackgroundLines
 */
void Heuristic::setVerticalBackgroundLines(vector<Line> verticalBackgroundLines2) {
    this->verticalBackgroundLines = verticalBackgroundLines2;
}

/**
 * Returns the vertical background lines.
 * 
 * @return verticalBackgroundLines
 */
vector<Line> Heuristic::getVerticalBackgroundLines() {
    return this->verticalBackgroundLines;
}

/**
 * Sets the horizontal foreground lines vector.
 * 
 * @param horizontalForegroundLines
 */
void Heuristic::setHorizontalForegroundLines(vector<Line> horizontalForegroundLines2) {
    this->horizontalForegroundLines = horizontalForegroundLines2;
}

/**
 * Returns the horizontal foreground lines vector.
 * 
 * @return horizontalForegroundLines
 */
vector<Line> Heuristic::getHorizontalForegroundLines() {
    return this->horizontalForegroundLines;
}

/**
 * Sets the vertical foreground lines vector.
 * 
 * @param verticalForegroundLines
 */
void Heuristic::setVerticalForegroundLines(vector<Line> verticalForegroundLines2) {
    this->verticalForegroundLines = verticalForegroundLines2;
}

/**
 * Returns the vertical foreground lines vector.
 * 
 * @return verticalForegroundLines
 */
vector<Line> Heuristic::getVerticalForegroundLines() {
    return this->verticalForegroundLines;
}

/**
 * Detects vertical background lines ant puts them into
 * verticalBackgroundLines vector. The lines are in the scale
 * of high resolution image.
 * 
 * @return vector of background vertical lines
 */
vector<Line> Heuristic::detectBackgroundVerticalLines() {
    vector<Line> verticalBackgroundLines;
    
    const double TAX_RELATIVE_TO_MINIMUM_HORIZONTAL = model->getTAX_RELATIVE_TO_MINIMUM_HORIZONTAL();
//    IplImage* imgInRGB = cvLoadImage(this->getROI()->getLogImagePath().c_str());
    IplImage* imgInRGB = cvCreateImage(cvGetSize(getROI()->getImageROI()), IPL_DEPTH_8U, 3);
    cvConvertImage(getROI()->getImageROI(), imgInRGB, CV_GRAY2RGB);
    
    // detect background lines in vertical
    for(int j = 0; j < getHorizontalProjection().size(); j++) {
        if(getHorizontalProjection().at(j) <= TAX_RELATIVE_TO_MINIMUM_HORIZONTAL * (getMaximumHorizontal() - getMinimumHorizontal()) + getMinimumHorizontal()) {
            CvPoint p3, p4; // (p4 - p3) = line
            p3.x = j;
            p3.y = 0;
            p4.x = j;
            p4.y = getROI()->getRightBottom().y - getROI()->getLeftUpper().y;
            
            verticalBackgroundLines.push_back(Line(p3, p4));
            
//            printf("vertical line: (%d,%d)-(%d,%d)\n", p3.x, p3.y, p4.x, p4.y);
            
//            cvLine(imgInRGB, p3, p4, CV_RGB(255,0,0));
        }
        else {
            verticalBackgroundLines.push_back(Line(cvPoint(-1,-1), cvPoint(-1,-1)));
        }
    }
    
    vector<Line> verticalBackgroundLinesDetectedAndFiltered = getFilter()->primaryFilter(verticalBackgroundLines);
    
//    vector<Line>::iterator it;
//    for(it = verticalBackgroundLinesDetectedAndFiltered.begin(); it != verticalBackgroundLinesDetectedAndFiltered.end(); it++) {
//        CvPoint p1 = (*it).p1, 
//                p2 = (*it).p2;
//        
//        vector<CvPoint> v;
//        v.push_back(p1);
//        v.push_back(p2);
//        GenericAdapter* adapter = new GenericAdapter(HIGH_2_LOW, meta);
//        vector<CvPoint> va = adapter->adaptPoints(v);
//        cvLine(imgInRGB, va[0], va[1], CV_RGB(255,0,0));
//        printf("background vertical: (%d,%d)-(%d,%d)\n", va[0].x, va[0].y, va[1].x, va[1].y);
        
//        cvLine(imgInRGB, p1, p2, CV_RGB(255,0,0));
//        printf("background vertical: (%d,%d)-(%d,%d)\n", p1.x, p1.y, p2.x, p2.y);
//    }
    
//    saveImageROI(imgInRGB);
//    cvReleaseImage(&imgInRGB);
    
    return verticalBackgroundLinesDetectedAndFiltered;
}

/**
 * Detects horizontal background lines and puts them into
 * horizontalBackgroundLines vector. The lines are in scale
 * of the high resolution image.
 * 
 * @return vector of background horizontal lines
 */
vector<Line> Heuristic::detectBackgroundHorizontalLines() throw (NullImageException) {
    vector<Line> horizontalBackgroundLines;
    
    const double TAX_RELATIVE_TO_MINIMUM_VERTICAL = model->getTAX_RELATIVE_TO_MINIMUM_VERTICAL();
    
//    IplImage* imgInRGB = cvLoadImage(this->getROI()->getLogImagePath().c_str());
    
//    if(imgInRGB == 0 ) {
//        throw NullImageException();
//    }
    
    // detect background lines in horizontal
    for(int i = 0; i < getVerticalProjection().size(); i++) {
        if(((getVerticalProjection().at(i)) <= TAX_RELATIVE_TO_MINIMUM_VERTICAL * (getMaximumVertical() - getMinimumVertical()) + getMinimumVertical()) 
                && (getVerticalProjection().at(i) >= getMinimumVertical())) {
            CvPoint p5, p6; // (p2 - p1) = line
            p5.x = 0;
            p5.y = i;
            p6.x = getROI()->getRightBottom().x - getROI()->getLeftUpper().x;
            p6.y = i;
                       
            horizontalBackgroundLines.push_back(Line(p5, p6));
            
//            cvLine(imgInRGB, p5, p6, CV_RGB(255,0,0));
            
            //printf("vertical line: (%d,%d)-(%d,%d)\n", p5.x, p5.y, p6.x, p6.y);
        }
        else {
            horizontalBackgroundLines.push_back(Line(cvPoint(-1, -1), cvPoint(-1,-1)));
        }
    }
    
    vector<Line> horizontalBackgroundLinesDetectedAndFiltered = getFilter()->primaryFilter(horizontalBackgroundLines);

//    vector<Line>::iterator it;
//    for(it = horizontalBackgroundLinesDetectedAndFiltered.begin(); it != horizontalBackgroundLinesDetectedAndFiltered.end(); it++) {
//        CvPoint p1 = (*it).p1, 
//                p2 = (*it).p2;
//        vector<CvPoint> v;
//        v.push_back(p1);
//        v.push_back(p2);
//        PointsAdapter* adapter = new PointsAdapter(HIGH_2_LOW, meta);
//        vector<CvPoint> va = adapter->adaptPoints(v);
//        cvLine(imgInRGB, va[0], va[1], CV_RGB(255,0,0));
//        printf("background horizontal: (%d,%d)-(%d,%d)\n", va[0].x, va[0].y, va[1].x, va[1].y);
        
//        cvLine(imgInRGB, p1, p2, CV_RGB(255,0,0));
//        printf("background horizontal: (%d,%d)-(%d,%d)\n", p1.x, p1.y, p2.x, p2.y);
//    }
    
//    saveImageROI(imgInRGB);
//    cvReleaseImage(&imgInRGB);
    
    return horizontalBackgroundLinesDetectedAndFiltered;
}

/**
 * Detects vertical foreground lines and puts them into
 * verticalForegroundLines vector.
 * 
 * @return vector of foreground vertical lines
 */
vector<Line> Heuristic::detectForegroundVerticalLines() {
    vector<Line> verticalForegroundLines;
    
    double TAX_RELATIVE_TO_MAXIMUM_VERTICAL = 0.9; // 0.5
    
    // PARA EXPERIMENTO ----------------------------------------------------------------
//    IplImage* imgInRGB = cvLoadImage(_roi->getLowResolutionImageCutPath().c_str());
    //----------------------------------------------------------------------------------
    
    IplImage* imgInRGB = cvCreateImage(cvGetSize(getROI()->getImageROI()), IPL_DEPTH_8U, 3);
    cvConvertImage(getROI()->getImageROI(), imgInRGB, CV_GRAY2RGB);
    
    // detect foreground lines in vertical
    for(int j = 0; j < getHorizontalProjection().size(); j++) {
        if(getHorizontalProjection().at(j) >= TAX_RELATIVE_TO_MAXIMUM_VERTICAL * (getMaximumHorizontal() - getMinimumHorizontal()) + getMinimumHorizontal()) {
            CvPoint p3, p4; // (p4 - p3) = line
            p3.x = j;
            p3.y = 0;
            p4.x = j;
            p4.y = getROI()->getRightBottom().y - getROI()->getLeftUpper().y;
            
            verticalForegroundLines.push_back(Line(p3, p4));
            
            //printf("vertical line: (%d,%d)-(%d,%d)\n", p3.x, p3.y, p4.x, p4.y);
            
//            cvLine(imgInRGB, p3, p4, CV_RGB(0,255,0));
        }
        else {
            verticalForegroundLines.push_back(Line(cvPoint(-1,-1), cvPoint(-1,-1)));
        }
    }
    
    vector<Line> verticalForegroundLinesDetectedAndFiltered = getFilter()->primaryFilter(verticalForegroundLines);
//    vector<Line>::iterator it;
//    for(it = verticalForegroundLinesDetectedAndFiltered.begin(); it != verticalForegroundLinesDetectedAndFiltered.end(); it++) {
//        CvPoint p1 = (*it).p1, 
//                p2 = (*it).p2;
//        cvLine(imgInRGB, p1, p2, CV_RGB(0,255,0));
//    }
    
    // draw borders
//    cvLine(imgInRGB, getUpLineTable().p1, getUpLineTable().p2, CV_RGB(255,0,0));
//    cvLine(imgInRGB, getDownLineTable().p1, getDownLineTable().p2, CV_RGB(255,0,0));
//    cvLine(imgInRGB, getLeftLineTable().p1, getLeftLineTable().p2, CV_RGB(255,0,0));
//    cvLine(imgInRGB, getRightLineTable().p1, getRightLineTable().p2, CV_RGB(255,0,0));
    
//    saveImageROI(imgInRGB);
//    cvReleaseImage(&imgInRGB);
    
    return verticalForegroundLinesDetectedAndFiltered;
}

/**
 * Detects horizontal foreground lines and puts them into
 * horizontalForegroundLines vector.
 * 
 * @return vector of foreground horizontal lines
 */
vector<Line> Heuristic::detectForegroundHorizontalLines() {
    vector<Line> horizontalForegroundLines;
    
    double TAX_RELATIVE_TO_MAXIMUM_HORIZONTAL = 0.9; // 0.5
//    IplImage* imgInRGB = cvLoadImage(this->getROI()->getLogImagePath().c_str());
    
    // detect foreground lines in horizontal
    for(int i = 0; i < getVerticalProjection().size(); i++) {
        if((getVerticalProjection().at(i)) >= TAX_RELATIVE_TO_MAXIMUM_HORIZONTAL * (getMaximumVertical() - getMinimumVertical()) + getMinimumVertical()) {
            CvPoint p5, p6; // (p2 - p1) = line
            p5.x = 0;
            p5.y = i;
            p6.x = getROI()->getRightBottom().x - getROI()->getLeftUpper().x;
            p6.y = i;
            
            horizontalForegroundLines.push_back(Line(p5, p6));
//            foregroundLinesDetected.push_back(p6);
            
//            cvLine(imgInRGB, p5, p6, CV_RGB(0,255,0));
            
//            printf("horizontal line: (%d,%d)-(%d,%d)\n", p5.x, p5.y, p6.x, p6.y);
        }
        else {
            horizontalForegroundLines.push_back(Line(cvPoint(-1,-1), cvPoint(-1,-1)));
        }
    }
    
    vector<Line> horizontalForegroundLinesDetectedAndFiltered = getFilter()->primaryFilter(horizontalForegroundLines);
//    vector<Line>::iterator it;
//    for(it = horizontalForegroundLinesDetectedAndFiltered.begin(); it != horizontalForegroundLinesDetectedAndFiltered.end(); it++) {
//        CvPoint p1 = (*it).p1, 
//                p2 = (*it).p2;
//        cvLine(imgInRGB, p1, p2, CV_RGB(0,255,0));
//    }
    
//    saveImageROI(imgInRGB);
//    cvReleaseImage(&imgInRGB);
    
    return horizontalForegroundLinesDetectedAndFiltered;
}
